﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CoreCalc;
using RayDen.Library.Components.PhotonMapping;
using RayDen.Library.Core;
using RayDen.Library.Core.Noise;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Data.Scene.SCFF;
using RayDen.Library.Data.Xml;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Renderer;
using RayDen.PowerConsole.Components;
using RayDen.RayEngine;

namespace PowerConsole
{
    class Program
    {
        private static uint Hash(int ix, int iy, int iz, int hashGridSize)
        {
            return (uint)(((ix * 73856093U) ^ (iy * 19349663U) ^ (iz * 83492791U)) % hashGridSize);
        }

        public class HashMap
        {
            private int pointsCount;
            private AABB bbox;
            private int[] Counts;
            private Photon[] Table;

            public HashMap()
            {

            }

            public void Construct(Photon[] points)
            {
                this.pointsCount = points.Length;
                bbox = new AABB(points.Select(item => item.Position));
                Counts = new int[pointsCount];
                Table = new Photon[pointsCount];
                var maxPhotonRadius = 1.2f;
                float cellSize = MathLab.Sqrt(maxPhotonRadius) * 2f;
                //std::cerr << "Hash grid cell size: " << cellSize <<std::endl;
                var invCellSize = 1f / cellSize;

                foreach (var point in points)
                {
                    Vector hh = (point.Position - bbox.Min) * invCellSize;
                    int ix = Math.Abs((int)(hh.x));
                    int iy = Math.Abs((int)(hh.y));
                    int iz = Math.Abs((int)(hh.z));
                    var hash = Hash(ix, iy, iz, pointsCount);
                    Table[hash] = point;
                    Interlocked.Add(ref Counts[hash], 1);
                }
            }

            public Photon[] Query(ref Point position, float radius)
            {
                var maxPhotonRadius = 16.2f;
                float cellSize = MathLab.Sqrt(maxPhotonRadius) * 2f;
                //std::cerr << "Hash grid cell size: " << cellSize <<std::endl;
                var invCellSize = 1f / cellSize;

                Vector hh = (position - bbox.Min) * invCellSize;
                int ix = Math.Abs((int)(hh.x));
                int iy = Math.Abs((int)(hh.y));
                int iz = Math.Abs((int)(hh.z));
                var hash = Hash(ix, iy, iz, pointsCount);
                var photon = Table[hash];

                if (IsNeighbour(ref position, ref photon.Position, radius))
                {
                    photon.SetPower(photon.Power * Counts[hash]);
                }
                return new[] { photon };
            }

            private bool IsNeighbour(ref Point p1, ref Point p2, float radius)
            {
                return (p2 - p1).Length <= radius;
            }

            private static uint Hash(int ix, int iy, int iz, int hashGridSize)
            {
                return (uint)(((ix * 73856093U) ^ (iy * 19349663U) ^ (iz * 83492791U)) % hashGridSize);
            }
        }

        static void v1()
        {
            var a = new SampledSpectrum(0.25f);
            var b = new SampledSpectrum(0.25f);
            var r = new SampledSpectrum(1f);

            SSE.MulSSE(ref a, ref  b, ref r);
        }

        static void Main(string[] args)
        {

            int itemCount = 1000;
            int iters = 100000;
            Random rand = new Random();
            int[] ints = Enumerable.Range(0, itemCount).Select(cc => rand.Next()).ToArray();
            HashSet<int> hs = new HashSet<int>(ints);
            List<int> lst = ints.ToList();
            bool contains = false;
            string result = string.Empty;
            result = PerformanceMeasurementController.Test(delegate
            {
                var index = rand.Next(0, itemCount - 1);
                contains = hs.Contains(index) != true;
            }, "HashSet search\r\n", iters);
            Console.WriteLine("Time "+result);

            result = PerformanceMeasurementController.Test(delegate
            {
                var index = rand.Next(0, itemCount - 1);
                contains = lst.BinarySearch(index) > 0;
            }, "List search\r\n", iters);
            Console.WriteLine("Time " + result);

            result = PerformanceMeasurementController.Test(delegate
            {
                var index = rand.Next(0, itemCount - 1);
                for (int j = 0; j < itemCount; j++)
                {
                    if (ints[j] == index)
                    {
                        contains = true;
                        break;
                    }
                }
            }, "Array enum search\r\n", iters);
            Console.WriteLine("Time " + result);

            return;

            IRandomProvider rp = new BaseQMCSequence();
            int i = 100;
            while (i!=0)
            {
                Console.WriteLine("{0}  {1:F6},  {2:F6},  {3:F6},  {4:F6}", i, rp.NextFloat(),rp.NextFloat(),rp.NextFloat(),rp.NextFloat());
                i--;
            }
            Console.WriteLine(rp.GetType().Name);
            return;
            /*
            RgbSpectrum c1 = new RgbSpectrum(0.5f, 0.7f, 1.55f);
            RgbSpectrum c2 = new RgbSpectrum(1.5f, 0.4f, 11f);
            RgbSpectrum c3 = new RgbSpectrum(0);

            
            SSE.MaddSse(ref c1, ref c2, 4f, ref c3);

            Console.WriteLine("SSE Version - {0}", c3);
            Console.WriteLine("CPU Version - {0}", (c1+c2)*4f);



            return;
            */

            v1();
            v1();
            v1();

            var res = new SampledSpectrum(1.9f);
            var rs = new SampledSpectrum(1.9f);

            var rgb_1 = ColorFactory.SSEToRgb(ref res);
            var rgb_2 = res.ToRgb();

            Console.WriteLine("SSE:{0} / Norm {1}", rgb_1, rgb_2);

            SSE.ClampSSE(ref res, ref rs);
            var maxIterations = 5;
            var testIterations = 1;
            var iterations = 100000000;
            var rnd = new FastRandom();

            var s1 = new SampledSpectrum(rnd.NextFloat());
            var s2 = new SampledSpectrum(rnd.NextFloat());
            var s3 = SampledSpectrum.UnitSpectrum();
            RgbSpectrum r3;
            float rx = 0f;
            float[][] rgb = Enumerable.Repeat(new[] {rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat()}, 1000).ToArray();

            var testActions = new[]
                {
                    //new Tuple<string, Action>("Spectral mul cpu", () =>
                    //{
                    //    s3 = s1*s2;
                    //}),

                    //new Tuple<string, Action>("Spectral mul sse", () =>
                    //{
                    //    SSE.MulSSE(ref s1, ref s2, ref s3);
                    //}),

                    new Tuple<string, Action>("Dot SSE", () =>
                    {
                        rx = SSE.Dot(ref s1, ref s2);
                    }),

                    new Tuple<string, Action>("Dot avx", () =>
                    {
                        rx = AVX.Dot(ref s1, ref s2);
                    }),

                    /*
                    new Tuple<string, Action>("to Rgb  cpu", () =>
                    {
                        r3 = s1.ToRgb();
                    }),

                    new Tuple<string, Action>("to Rgb  sse", () =>
                    {
                        r3 = ColorFactory.SSEToRgb(ref s1);
                    }),*/

                    //new Tuple<string, Action>("Rgb madd cpu", () =>
                    //{
                    //    var r1 = new RgbSpectrum(rnd.NextFloat());
                    //    var r2 = new RgbSpectrum(rnd.NextFloat());
                    //    var r4 = new RgbSpectrum(rnd.NextFloat());

                    //    r3 = (r1 + r2+r4+r1) * (1f / 3f);

                    //    //SSE.MaddSse(ref r1, ref c2, 4f, ref c3);

                    //}), 

                    //new Tuple<string, Action>("Rgb madd sse", () =>
                    //{
                    //    var r1 = new RgbSpectrum(rnd.NextFloat());
                    //    var r2 = new RgbSpectrum(rnd.NextFloat());
                    //    var r4 = new RgbSpectrum(rnd.NextFloat());


                    //    SSE.AverageSSE(ref r1, ref r2, ref r4,ref r1, ref r3);

                    //}), 

                    /*
                    new Tuple<string, Action>    (" SSE to rgb ", () =>
                        {
                            foreach (var f in rgb)
                            {
                                ColorFactory.SSEFromRgb(f, SpectrumType.Illuminant);
                                
                            }
                        }),
                         new Tuple<string, Action>    (" AVX to rgb ", () =>
                        {
                            foreach (var f in rgb)
                            {
                                ColorFactory.AVXFromRgb(f, SpectrumType.Illuminant);
                            }
                        }),
                        new Tuple<string, Action>(" CPU to rgb ", () =>
                        {
                            foreach (var f in rgb)
                            {
                                ColorFactory.BaseFromRgb(f, SpectrumType.Illuminant);

                            }
                        }),
                     */ 
/*

                          new Tuple<string, Action>    (" Normal sqrt ", () =>
                        {
                            for (int i = 0; i < 4; i++)
                            {
                                var f = rnd.NextFloat();
                                res[0] = MathLab.Sqrt(f);
                            }
                        }),
                        new Tuple<string, Action>(" SSE sqrt    ", () =>
                        {
                            SSE. VSqrt();
                        }),*/
                        /*
                    new Tuple<string, Action>("Current NextFloat :",  () =>
                        {
                            var rnd = new FastRandom();
                            rnd.NextFloat();
                            for (var i = 0; i < iterations; i++)
                            {
                                rnd.NextFloat();
                            }
                        }),
                    new Tuple<string, Action>("Halton : ",  () =>
                        {
                            var qmc = new HaltonSequence();
                            for (var i = 0; i < iterations; i++)
                            {
                                //rnd.NextDouble();
                                qmc.NextFloat();
                            }
                        }),
                    new Tuple<string, Action>("VanDerCorrupt : ",  () =>
                        {
                            var qmc = new BaseQMCSequence();
                            for (var i = 0; i < iterations; i++)
                            {
                                //rnd.NextDouble();
                                qmc.NextFloat();
                            }
                        }),*/

                };
        @test:

            Console.WriteLine("Converting 1000 float triplets to SampledSpectrum");
            Console.WriteLine("Running test for {0} iterations per test . {1} test iteration", iterations, testIterations);
            //TestExpressions();
            foreach (var testAction in testActions)
            {
                Tuple<string, Action> action = testAction;
                Console.WriteLine(PerformanceMeasurementController.Test(() => action.Item2(), 8,testAction.Item1, iterations));
            }
            testIterations++;
            if (testIterations > maxIterations)
                return;
            goto @test;


            /*
            QualitySettingsInfo qs = new QualitySettingsInfo() { SamplesPerPixel = 32, ShadowRaysPerLight = 1, TextureSamplingQuality = TextureSamplingQuality.Linear, SuperSamplingSize = 1 };
            OutputSettingsInfo os = new OutputSettingsInfo() { FilePath = @"G:\Git", Width = 640, Height = 480, GammaCorrection = true, BitsPerPixel = 32, ToneMap = false, ToneMapValue = 1.5f };

            var qs_s = SerializationService.Serialize(qs, typeof(QualitySettingsInfo), typeof(TextureSamplingQuality));
            var os_s = SerializationService.Serialize(os, typeof(OutputSettingsInfo));
            Console.WriteLine(qs_s);
            Console.WriteLine(os_s);*/

            /*
            var f = new [] {0.25f, 0.15f, 0.15f, 0.45f};
            float[] cdf = new float[f.Length+1];
            float c;
            MC.ComputeStep1dCDF(f, 4, cdf, out c);
            Console.WriteLine();
            return;
           */
            //CreateFrame(@"F:\Dev\Frames\RDF\", "cornell_service", @"F:\Dev\Scenes\OasisCornell\");
            //CreateFrame(@"F:\Dev\Frames\RDF\", "decostreet", @"F:\3D\Models\decostreet\");
            //            CreateFrame(@"F:\Dev\Frames\RDF\", "tmp", @"F:\3D\Models\decostreet\");
            /*
            GlobalConfiguration.Instance.OpenConfig(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Global.Config.xml");
            GlobalConfiguration.Instance.Add("TextureFindPaths", new string[3] { @"C:\Dev\", @"F:\3d\HDRi\", @"F:\3d\Textures\" });
            GlobalConfiguration.Instance.SaveConfig(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Global.Config.xml");


            //CreateFrame(@"F:\Dev\Frames\CompressedDataFrames\DecoStreet\", "decostreet", @"F:\Dev\Frames\CompressedDataFrames\DecoStreet\");
            return;
            //
            //CreateFrame(@"F:\Dev\Frames\RDF\", "cornell_service");
            using (var frame = ArchiveFrameService.OpenArchive(@"F:\Dev\Frames\CompressedDataFrames\OasisCornell\OasisCornell.cdf",
                                                @"F:\Temp\RayDenTemp\OasisCornell"))
                Console.WriteLine("Frame opened {0}", frame.Frame.FrameName);
             * */
        }

        private static void TestExpressions()
        {
            /* Wood
             double	s = pow ( SineWave ( RingSpacing*sqrt ( x*x+y*y ) + TurbScale * Turbulence ( p, 3 ) ), Squeeze );
         	 t.Color = ( 1 - s ) * LightWood + s * DarkWood;
            string wood = "unit-$pow<$saw<3*sqrt<x*x+y*y*>+1.8*$t<c,3>>,5>";
             */
            string wood = "(unit-($saw<3*$sqrt<x*x+y*y*>>+$t<c,3>))*lw + ( ($saw<3*$sqrt<x*x+y*y*>>+$t<c,3>)*dw)";
            string expression = wood;
            //@"unit*$sqrt<x*x+y*y>";
            float x = 0.5f;
            float y = 0.7f;
            var p = new Vector(x, y, 2 - x - y);
            var p0 = new Vector(x, y, 0);
            var u = new RgbSpectrum(1f);

            var rnd = new FastRandom();
            var c = new RgbSpectrum(NoiseProvider.Instance.Noise3D(p));
            var evaluator = new GenericExpressionEvaluator<RgbSpectrum>((i) =>
                {
                    if (i is float)
                    {
                        return new RgbSpectrum((float)i);
                    }
                    if (i is double)
                    {
                        return new RgbSpectrum((float)((double)i));
                    }
                    if (i is int)
                    {
                        return new RgbSpectrum((int)i);
                    }
                    if (i is Int64)
                    {
                        return new RgbSpectrum((int)i);
                    }
                    return (RgbSpectrum)i;
                }, RgbSpectrum.FromStringData);

            var func = evaluator.Compile(expression);
            var f = rnd.NextDouble();
            Console.WriteLine("Script version: {0}", func(new
                {
                    e = f,
                    c,
                    u,
                    unit = RgbSpectrum.UnitSpectrum(),
                    lw = new RgbSpectrum(0.5f, 0.2f, 0.3f),
                    dw = new RgbSpectrum(0.3f, 0.1f, 0.1f),
                    x,
                    y,
                    arr = new[] { 0.5, 0.6, 0.6 },
                    n = c * 0.5f,
                }));

            Console.WriteLine("C# version : {0}", c * Math.Sqrt(x * x + y * y));

            return;
        }

        private static void TestDirections(FastRandom rnd)
        {
            int dirCount = 80;
            var directions = new List<Vector>(dirCount);

            for (int i = 0; i < dirCount; i++)
            {
                var dir = new Vector(1f - 2f * rnd.NextFloat(), 1f - 2f * rnd.NextFloat(), 1f - 2f * rnd.NextFloat());
                directions.Add(dir);
            }
            directions.Sort((a, b) => Geometry.ClassifyDirection(ref a).CompareTo(Geometry.ClassifyDirection(ref b)));

            for (int index = 0; index < directions.Count; index++)
            {
                var dir = directions[index];
                Console.WriteLine("Vector {0}  Classification {1}", dir, Geometry.ClassifyDirection(ref dir));
            }
        }

        private static void Hashtest()
        {
            float worldSize = 100f;
            AABB hpbbox = new AABB(new Point(-worldSize), new Point(worldSize));
            int pointsCount = 1000000;

            var rnd = new FastRandom();
            HashMap map = new HashMap();
            List<Photon> pt = new List<Photon>();
            for (int i = 0; i < pointsCount; i++)
            {
                var hitPoint = new Point(worldSize * rnd.NextFloatNeg(), worldSize * rnd.NextFloatNeg(),
                                         worldSize * rnd.NextFloatNeg());
                if (!hpbbox.Inside(hitPoint))
                {
                    Console.WriteLine("Generated invalid point");
                    continue;
                }

                Vector dir = new Vector(worldSize * rnd.NextFloatNeg(), worldSize * rnd.NextFloatNeg(), worldSize * rnd.NextFloatNeg());
                RgbSpectrum spec = new RgbSpectrum(rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat());

                pt.Add(new Photon(ref hitPoint, ref dir, ref spec));
            }

            map.Construct(pt.ToArray());

            var qp = new Point(1f);

            var query = map.Query(ref qp, 2f);
            foreach (var photon in query)
            {
                Console.WriteLine(photon);
            }

            Console.WriteLine("Bounding box size {0}, Points {1}", worldSize, pointsCount);

            return;
        }

        private static void CreateFrame(string outputDirectory, string frameName, string workingDir)
        {
            Console.WriteLine("Starting frame {0}.rdf", frameName);
            FrameDescription newFrame = new FrameDescription()
                {
                    WorkingDir =
                        //@"F:\3D\Models\3DSMAX\"
                        // @"F:\Dev\Scenes\OasisCornell\"
                       workingDir
                };
            newFrame.Add("ImageWidth", 1280);
            newFrame.Add("ImageHeight", 720);
            newFrame.AddNew(
                new FrameCamera()
                    {
                        Name = "Main Camera",
                        Position = new Vector(-132.237f,
                                              -26.684f,
                                              15.514f),
                        Target = new Vector(-69.374f,
                                            0.0f,
                                            11.601f
                    ),
                        Up = new Vector(0f, 0f, 1f),
                        Fov = 35f,
                    });

            newFrame.AddNew(
              new FrameCamera()
              {
                  Name = "Camera #2",
                  Position = new Vector(106.499f,
                                        -12.832f,
                                        3.318f),
                  Target = new Vector(50.815f,
                                     -11.904f,
                                     3.318f
              ),
                  Up = new Vector(0f, 0f, 1f)
              });

            newFrame.AddNew(
                new FrameObjFileReference()
                    {
                        MtlFilePath = frameName + ".mtl",
                        ObjFilePath = frameName + ".obj"
                    });
            newFrame.AddNew(
                new FrameLightsource()
                    {
                        LightType = LightSourceTypes.Sky,
                        Parameters = new ConfigurableEntity()
                          {
                                new KeyValuePair<string, object>(FrameLightsource.SkyLightSunDir, new Vector(0f,
                                                                                                                 1.845f,
                                                                                                                 1.216f)),
                                new KeyValuePair<string, object>(FrameLightsource.SkyLightTurbulence, 2.5f),
                            }
                    });
            newFrame.AddNew(
                new FrameLightsource()
                    {
                        LightType = LightSourceTypes.Point,
                        Parameters = new ConfigurableEntity()
                            {
                                new KeyValuePair<string, object>(FrameLightsource.PointLightPosition, new Vector(-63.798f,
                                                                                                                 -110.845f,
                                                                                                                 58.216f)),
                                new KeyValuePair<string, object>(FrameLightsource.LightsourcePower, new RgbSpectrum(3.78f)),
                            }
                    }
                );
            newFrame.AddNew(
                new FrameLightsource()
                    {
                        LightType = LightSourceTypes.Point,
                        Parameters = new ConfigurableEntity()
                            {
                                new KeyValuePair<string, object>(FrameLightsource.PointLightPosition, new Vector(150.607f,
                                                                                                                 6.237f,
                                                                                                                 28.866f)),
                                new KeyValuePair<string, object>(FrameLightsource.LightsourcePower, new RgbSpectrum(3.78f)),
                            }
                    }
                );
            newFrame.AddNew(
                new FrameLightsource()
                    {
                        LightType = LightSourceTypes.EnvironmentMap,
                        Parameters = new ConfigurableEntity() { new KeyValuePair<string, object>(FrameLightsource.InfiniteLightMapPath, @"skylight-blue.exr") }
                    }
                );

            //  newFrame.AddNew(
            //new FrameLightsource()
            //{
            //    LightType = LightSourceTypes.Point,
            //    Parameters = new ConfigurableEntity()
            //              {
            //                  new KeyValuePair<string, object>(FrameLightsource.PointLightPosition, new Vector(133.19f,
            //                                                                                                   592.194f,
            //                                                                                                   714.1383f)),
            //                  new KeyValuePair<string, object>(FrameLightsource.LightsourcePower,   new RgbSpectrum(0.78f)),
            //              }
            //}
            //);
            //  newFrame.AddNew(
            //      new FrameLightsource()
            //          {
            //              LightType = LightSourceTypes.EnvironmentMap,
            //              Parameters = new ConfigurableEntity() { new KeyValuePair<string, object>(FrameLightsource.InfiniteLightMapPath, @"F:\3D\HDRI\skylight-blue.exr") }
            //          }
            //      );
            Console.WriteLine("Writing file " + string.Format("{0}{1}.rdf", outputDirectory, frameName));
            Console.WriteLine(SerializationService.SerializeKnown(newFrame));
            using (
                var fs = new FileStream(string.Format("{0}{1}.rdf", outputDirectory, frameName), FileMode.OpenOrCreate,
                                        FileAccess.Write))
            {
                using (var wr = new StreamWriter(fs))
                {
                    wr.Write(SerializationService.SerializeKnown(newFrame));
                }
            }
        }
    }
}
